home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Mac Magazin/MacEasy 19
/
Mac Magazin and MacEasy Magazine CD - Issue 19.iso
/
Musik & Kunst
/
Ear Workout 2.1
/
source code
/
ear_sing_interval.cp
< prev
next >
Wrap
Text File
|
1996-01-15
|
17KB
|
528 lines
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <OSUtils.h>
#include <QuickDraw.h>
#include <Sound.h>
#include <SoundInput.h>
#define NEED_MAC_STUFF 1
#include "Ninkasi:C++ util:generic.h"
#include "Ninkasi:C++ util:complete_window.h"
#include "ear_defines.h"
#include "ear_decl.h"
#include "ear_prototypes.h"
DEN_MOTHER_T interval_den_mother;
void find_actual_sample(UBYTE_T **where,ULONG_T *nbytes,Handle snd_handle);
extern double sin(),cos(),exp(),log(),fabs(),sqrt();
void name_of_interval(char *name,int interval);
#define MAX_SAMPLE_SIZE 100000
#define MIN_THRESHOLD 7
// average abs val of deviation from mean of their sampled singing
// should be at least this much
int
get_period(
unsigned char *data, //-- your data
int n, //-- number of data points
int lo_p, //-- shortest possible period
int hi_p, //-- longest possible period
short *grid, //-- storage you allocate for me (call me with n=0
// to find out how many bytes)
int grid_space, //-- number of bytes you allocated for me
int *naverage, //-- number of periods averaged together to get this
// result (output)
short **nhit_ptr, //--pointer to the histogram; 256 bins per octave;
// bin number=256*log2(p/lo_p)
int *nhist, //--tells them how many bins in histogram,
int **int_info_handle, //--Passes back a pointer to some handy data:
// *int_info[0]=mean abs. val of deviation from mean
// *int_info[1]=mean abs. val of derivative
// (Means are only based on a subset of the
// data.)
// *int_info[2]=xshift
// *int_info[3]=yshift
// *int_info[4]=tstep
double **double_info_handle
//
);
int closest_integer(double x);
void
sing_interval_den_mother(complete_window *my_complete_window)
{
//--- decoder string for controls:
static char *decoder_ptr =
"\0request,result,play,record,go_on,t1,t2,t3";
//--- has to begin with null so subroutines know it's not really
// a Pascal string
static char **decoder_string = &decoder_ptr;
//--- keeping track of my current state:
static int first_time = 1;
static int they_have_interval_to_consider = 0;
#define TYPICAL_TEMPO 105
static double typical_tempo = TYPICAL_TEMPO;
static int mean_note = 66;
static int interval_enabled[MAX_INTERVAL+1];
//--- about the current interval:
static int note1,note2,duration,rest_between,interval,center,abs_i;
static int lower_note = -999;
static int higher_note = -999;
//--- about their sampled singing:
static int have_sample = 0;
static OSErr snd_record_result;
static double raw_sampling_freq = 22254.54545;
static double sampling_freq;
static int nbytes;
static UBYTE_T *where_is_actual_sample;
static int interval_they_sang;
static double amount_sharp_or_flat;
// The following are globals, so wrap_it_up() can deallocate the sample:
// int have_sample_storage = 0;
// static Handle sound_handle = (Handle) 0;
static int have_analysis = 0;
static int period;
double freq,note;
int *period_int_info;
double *period_double_info;
static int have_storage_for_get_period = 0;
static short **get_period_storage_handle;
static int size_of_get_period_storage;
static short *period_hist;
static int nperiod_hist;
static int analysis_succeeded;
int need_to_redraw,need_to_redraw_all,play_it,ready_to_update,record_it,
redraw_request,they_want_to_record,just_gave_them_interval_to_consider,
they_hit_go_on;
static int difficulty_of_interval[MAX_INTERVAL+1]=
{0, 1,1, 1,1, 1, 3,1, 2,2, 3,3, 0, 3,3, 3 };
// 1 2 3 4 5 6 7 8 9 10
static int difficulty = 3;
static int need_to_analyze = 0;
static int need_to_tell_them_what_they_sang = 0;
char nifty_name[100];
int nifty_index,recognized;
GrafPtr save_graf;
double tempo;
int min_duration,max_duration;
need_to_redraw = 0;
need_to_redraw_all = 0;
redraw_request = 0;
play_it = 0;
they_want_to_record = 0;
just_gave_them_interval_to_consider = 0;
they_hit_go_on = 0;
if (first_time) {
first_time = 0;
for (int i=0; i<=MAX_INTERVAL; i++) {
interval_enabled[i] = (i!=0) && (i<=12);
}
}
switch(my_complete_window->whassup) {
case complete_window_created:
need_to_redraw = 1;
need_to_redraw_all = 1;
break;
case complete_window_redraw:
need_to_redraw = 1;
need_to_redraw_all = 1;
ready_to_update = 1; //-- main program does begin update & sets graf port
break;
case complete_window_action:
part_number_to_nifty_label(nifty_name,&nifty_index,
*decoder_string,my_complete_window->part_number);
if (nifty_index != -999) {
recognized = 0;
if (strcmp(nifty_name,"play")==0) {
play_it = 1;
}
if (strcmp(nifty_name,"record")==0) {
they_want_to_record = 1;
}
if (strcmp(nifty_name,"go_on")==0) {
they_hit_go_on = 1;
}
}//--end if they hit a valid control
break;
case complete_window_erase:
they_have_interval_to_consider = 0;
first_time = 1;
sing_interval_window_exists = 0;
if (have_sample_storage) {
if (VALID_HANDLE(sound_handle)) {
HUnlock(sound_handle);
DisposHandle(sound_handle);
}
have_sample_storage = 0;
}
return;
}
if (they_hit_go_on)
they_have_interval_to_consider = 0;
if (!they_have_interval_to_consider) {
int previous_lower,previous_higher;
previous_lower = lower_note;
previous_higher = higher_note;
min_duration = .7 * 2000.*60./typical_tempo;
max_duration = 4 * 2000.*60./typical_tempo;
do {
tempo = typical_tempo*(1.+.2*(random_double()+random_double()-1.));
duration = 2000.*60./tempo;
} while (duration>max_duration || duration<min_duration);
if (difficulty<1) difficulty=1; // avoid infinite loop...
do {
abs_i = random_integer(MAX_INTERVAL+1)-1;
if (random_double()<.5)
interval = abs_i;
else
interval = -abs_i;
center = mean_note+(random_double()-.5)*8+(random_double()-.5)*8;
/* mean_note plus or minus an something, biased towards
pitches close to mean_note */
lower_note = center-interval/2;
higher_note = lower_note+interval;
if (interval<0) {
note1 = higher_note;
note2 = lower_note;
}
else {
note1 = lower_note;
note2 = higher_note;
}
} while (random_integer(difficulty)<difficulty_of_interval[abs_i]
|| (previous_lower==lower_note && previous_higher==higher_note));
play_it = 1;
they_have_interval_to_consider = 1;
just_gave_them_interval_to_consider = 1;
need_to_redraw = 1;
redraw_request = 1;
have_analysis = 0;
}//---end if need to choose new interval
//--- This is the first block of drawing code, including the code
// that tells them what interval to sing. There is a second block,
// which includes the code that tells them what they actually sang.
if (need_to_redraw) {
if (!ready_to_update) {
GetPort(&save_graf);
SetPort(my_complete_window->the_window);
}
if (need_to_redraw_all) {
normal_text_style();
refresh_text(my_complete_window,"t1",1,*decoder_string);
refresh_text(my_complete_window,"t2",1,*decoder_string);
TextFont((short) newYork);
TextSize((short) 10);
refresh_text(my_complete_window,"t3",1,*decoder_string);
normal_text_style();
}
if (redraw_request || need_to_redraw_all) {
Handle h;
char s[50],interval_name[30];
int i,tot;
get_item_by_nifty_label(my_complete_window->the_window,
"request",1,*decoder_string,&h);
if (VALID_HANDLE(h)) {
name_of_interval(interval_name,abs_i);
if (abs_i != 0)
sprintf(s+1,"%s %s",(interval<0 ? "descending" : "ascending"),
interval_name);
else
sprintf(s+1,"%s",interval_name);
s[0] = strlen(s+1);
TextFont((short) newYork);
SetIText(h,(unsigned char *) s);
normal_text_style();
}
}
if (!ready_to_update) {
SetPort(save_graf);
}
} //---end if need to redraw
if (play_it) {
int notes[4];
notes[0] = note1;
play_chord(1,my_snd_chan,notes,duration,1);
}
if (they_want_to_record
// || just_gave_them_interval_to_consider
// ...made it impossible to get out of modal dialog
) {
Point corner;
ULONG_T i;
if (have_sample_storage) {
if (VALID_HANDLE(sound_handle)) {
HUnlock(sound_handle);
DisposHandle(sound_handle);
}
have_sample_storage = 0;
}
sound_handle = NewHandle((Size) MAX_SAMPLE_SIZE);
if (sound_handle == (Handle) 0) {
static char s[75];
sprintf(s,"Out of memory, recording sound, error=%d",
(int) MemError());
bail_out(s);
}
else
have_sample_storage = 1;
SetPt(&corner,300,100);
HUnlock(sound_handle);
snd_record_result = SndRecord((ModalFilterProcPtr) 0,corner,siBestQuality,&sound_handle);
HLock(sound_handle);
if (snd_record_result == userCanceledErr) SysBeep((short) 0);
if (snd_record_result==0) {
ULONG_T nbytes_raw;
have_sample = 1;
find_actual_sample(&where_is_actual_sample,&nbytes_raw,sound_handle);
if (0) { //---- test with sine wave
int i;
watch_cursor();
for (i=0; i<nbytes_raw; i++)
where_is_actual_sample[i] = 128+60*sin(.4+2.*3.1416*440./raw_sampling_freq*i);
arrow_cursor();
}
if (0) { //---- test with zero
int i;
for (i=0; i<nbytes_raw; i++)
where_is_actual_sample[i] = 128;
}
nbytes = nbytes_raw;
sampling_freq = raw_sampling_freq;
}
if (have_sample) {
need_to_analyze = 1;
need_to_tell_them_what_they_sang = 1;
need_to_redraw = 1;
}
}
if (need_to_analyze && have_sample) {
int lo_p,hi_p,naverage;
if (!have_storage_for_get_period) {
size_of_get_period_storage = get_period((unsigned char *) 0,
0,0,0,(short *) 0,0,(int *) 0,&period_hist,&nperiod_hist,
(int **) 0,(double **) 0);
get_period_storage_handle = (short **) NewHandle((Size) size_of_get_period_storage);
if (VALID_HANDLE(get_period_storage_handle)) {
have_storage_for_get_period = 1;
}
else
bail_out("out of memory");
}
lo_p = 20;
hi_p = 300;
watch_cursor();
period = get_period(where_is_actual_sample,(int) nbytes,
lo_p,hi_p,
(short *) *get_period_storage_handle,
size_of_get_period_storage,
&naverage,&period_hist,&nperiod_hist,
&period_int_info,&period_double_info);
arrow_cursor();
analysis_succeeded =
(period!=0) && (period_int_info[0]>=MIN_THRESHOLD);
if (analysis_succeeded) {
freq = 256.*raw_sampling_freq/period;
note = 60.+12.*log(freq/261.6)/log(2.);
interval_they_sang = closest_integer(note-note1);
amount_sharp_or_flat = note-note1-interval_they_sang;
}
have_analysis = analysis_succeeded;
need_to_analyze = 0;
}
//--- This is the second block of drawing code, including the code
// that tells them what they actually sang.
if (need_to_redraw) {
if (!ready_to_update) {
GetPort(&save_graf);
SetPort(my_complete_window->the_window);
}
if (need_to_tell_them_what_they_sang || need_to_redraw_all) {
Handle h;
char s[300],s2[300],interval_name[300],inverted_interval_name[300];
int i,tot;
get_item_by_nifty_label(my_complete_window->the_window,
"result",1,*decoder_string,&h);
if (VALID_HANDLE(h)) {
if (have_sample) {
if (have_analysis) {
name_of_interval(interval_name,interval_they_sang);
// If they sang an interval in the opposite direction to what
// was requested, maybe they sang the right note, but displaced
// an octave. Tell them what the inversion would be:
if (interval*interval_they_sang<0) {
name_of_interval(inverted_interval_name,
inversion_of_interval(interval_they_sang));
sprintf(s2," (inversion would be %s)",inverted_interval_name);
}
else
sprintf(s2,"");
sprintf(s+1,"%s %s, %4.1lf semitones %s%s",
(interval_they_sang==0 ? "" :
(interval_they_sang<0 ? "descending" : "ascending")),
interval_name,
fabs(amount_sharp_or_flat),
(amount_sharp_or_flat>0 ? "sharp" : "flat"),
s2
/*
(int) period,(double) freq,(int) note1,(double) note,
period_int_info[0],period_int_info[1],
period_int_info[2],period_int_info[3],
period_int_info[4]
*/
);
need_to_tell_them_what_they_sang = 0;
}
else {
if (period_int_info[0]<1)
sprintf(s+1,"No sound was detected. You may not have a microphone installed.");
else
sprintf(s+1,"Try singing louder, or closer to the microphone.");
}
}
else
sprintf(s+1,""); //-- don't have any analysis, haven't recorded a sample
s[0] = strlen(s+1);
TextFont((short) newYork);
SetIText(h,(unsigned char *) s);
normal_text_style();
}
}
if (!ready_to_update) {
SetPort(save_graf);
}
} //---end if need to redraw
}
int
closest_integer(double x)
{
int y;
y = x+.5;
while(fabs(y+1.-x)<fabs(y-x)) ++y;
while(fabs(y-1.-x)<fabs(y-x)) --y;
return y;
}
void
name_of_interval(char *interval_name,int interval)
{
int abs_i,octave_bump;
abs_i = interval;
if (abs_i<0) abs_i = -abs_i;
octave_bump = 0;
if (abs_i>15) {
while(abs_i>=12) {
abs_i -= 12;
++octave_bump;
}
}
switch(abs_i) {
case 0: strcpy(interval_name,"unison"); break;
case 1: strcpy(interval_name,"b2"); break;
case 2: strcpy(interval_name,"2"); break;
case 3: strcpy(interval_name,"b3"); break;
case 4: strcpy(interval_name,"3"); break;
case 5: strcpy(interval_name,"4"); break;
case 6: strcpy(interval_name,"b5"); break;
case 7: strcpy(interval_name,"5"); break;
case 8: strcpy(interval_name,"b6"); break;
case 9: strcpy(interval_name,"6"); break;
case 10: strcpy(interval_name,"b7"); break;
case 11: strcpy(interval_name,"7"); break;
case 12: strcpy(interval_name,"octave"); break;
case 13: strcpy(interval_name,"b9"); break;
case 14: strcpy(interval_name,"9"); break;
case 15: strcpy(interval_name,"b10"); break;
default: strcpy(interval_name,"--"); break;
}
if (octave_bump>0) {
char s[100];
switch(octave_bump) {
case 1:
strcat(interval_name," + octave");
break;
default:
sprintf(s," + %d octaves",octave_bump);
strcat(interval_name,s);
break;
}
}
}
#define UBYTE_AT(x) (* (UBYTE_T *) (x))
#define UWORD_AT(x) (* (UWORD_T *) (x))
#define ULONG_AT(x) (* (ULONG_T *) (x))
// Returns the address of the actual sound data, yes the actual honest
// to god bytes, not the header in front or the header in front of the
// header. If there's more than one sound command, will only return
// the data correspinding to the first one.
// Although we don't move memory in here, it's probably safest to
// lock the sample before calling this routine.
void
find_actual_sample(UBYTE_T **where,ULONG_T *nbytes,Handle snd_handle)
{
UWORD_T nsynth,ncmds,cmd;
ULONG_T par2;
UBYTE_T *find_it;
UBYTE_T *snd;
snd = * (UBYTE_T **) snd_handle;
nsynth = UWORD_AT(snd+2);
ncmds = UWORD_AT(snd+4+nsynth*2+4);
cmd = UWORD_AT(snd+4+nsynth*2+4+2);
par2 = ULONG_AT(snd+4+nsynth*2+4+2+4);
if (cmd & dataOffsetFlag)
find_it = snd+par2;
else
find_it = * (UBYTE_T **) &par2;
find_it += 4; //---- THINK Reference seems to leave out a longword,
// so bump past it!?!?
*nbytes = ULONG_AT(find_it);
*where = find_it + 4*4+2;
}